<?php
/**
 * The filter class for form data. <br />
 *
 * @author Liuxiao<seanliu707@gmial.com>
 * @copyright Swolf 2015
 * @since 1.0
 */

//define filter type
defined('SNULL')        or define( 'SNULL',         1 << 0 );	//匹配 null
defined('STRING')       or define( 'STRING',        1 << 1 );	//字符串
defined('NUMERIC')      or define( 'NUMERIC',       1 << 2 );	//匹配数字
defined('EMAIL')        or define( 'EMAIL',         1 << 3 );	
defined('LATIN')        or define( 'LATIN',         1 << 4 );
defined('PHONE')        or define( 'PHONE',         1 << 5 );
defined('URL')          or define( 'URL',           1 << 6 );
defined('QQ')           or define( 'QQ',            1 << 7 );
defined('DATE')         or define( 'DATE',          1 << 8 );
defined('ZIP')          or define( 'ZIP',           1 << 9 );
defined('IDNUM')        or define( 'IDNUM',         1 << 10 );
defined('EQUAL')        or define( 'EQUAL',         1 << 11 );	//判断相等
defined('EQUAL_LOWER')  or define( 'EQUAL_LOWER',   1 << 12 );	//不区分大小写相等
defined('IN_VALUES')    or define( 'IN_VALUES',     1 << 13 );	//判断某个值有没有在一组值当中
defined('TEL_PHONE')    or define( 'TEL_PHONE',     1 << 14 );	//固话验证
 
//sanitize constants
defined('SANITIZE_SCRIPT')  or define( 'SANITIZE_SCRIPT',   1 << 0 );
defined('SANITIZE_INT')     or define( 'SANITIZE_INT',      1 << 1 );
defined('SANITIZE_TRIM')    or define( 'SANITIZE_TRIM',     1 << 2 );
defined('SANITIZE_HTML')    or define( 'SANITIZE_HTML',     1 << 3 );
defined('MAGIC_QUOTES')     or define( 'MAGIC_QUOTES',      1 << 4 );

function SLIMIT($l, $r = -1) 
{ 
    return ($r == -1 ? array(0,$l) : array(0,$l,$r)); 
}
function SRANGE($l, $r = -1) 
{ 
    return ($r == -1 ? array(1,$l) : array(1,$l,$r)); 
}

class Filter {
	//is all white spase
	private static function isWhiteSpace( &$str ) 
	{
		return ( $str == '' || preg_match( '/^\s+$/' , $str ) );
	}
	//is validate basic latin
	private static function isLatin( &$str ) 
	{
		return preg_match( '/^[a-z0-9_]+$/i' , $str );
	}
	//is validate phone number ^[0-9]{3,12}$
	private static function isPhone( &$str ) 
	{
		return preg_match( '/^1[34578]{1}[0-9]{9}|0[0-9]{2,3}\-?[0-9]{7,8}(\-[0-9])?$/' , $str );
	}
	//is validate qq number
	private static function isQq( &$str ) 
	{
		return preg_match( '/^[1-9][0-9]{4,}$/' , $str );
	}
	//is validate date: 2013-01-01
	private static function isDate( &$str ) 
	{
		return preg_match( '/^[1-9][0-9]{3}-(0[1-9]|10|11|12)-([0-2][0-9]|30|31)$/' , $str );
	}
	//is validate ZIP
	private static function isZip( &$str ) 
	{
		return preg_match( '/^[1-9]\d{5}(?!\d)$/' , $str );
	}
	//is validate id card number
	private static function isIdnum( &$str ) 
	{
		return preg_match( '/^\d{15}|[1-6]\d{5}[1-9]\d{3}(0[1-9]|10|11|12)([0-2][0-9]|30|31)\d{3}[0-9A-Z]$/' , $str );
	}
	//is validate email address
	private static function isEmail( &$str ) 
	{
		return ( filter_var( $str , FILTER_VALIDATE_EMAIL ) !== FALSE );
	}
	//is validate url 
	private static function isUrl( &$str ) 
	{
		return ( filter_var( $str , FILTER_VALIDATE_URL ) !== FALSE );
	}
	
	//sanitize the script
	private static function sanitizeScript( &$str ) 
	{
		$rules = array( '/<script(.*?)>(.*?)<\/script(.*?)>/i' , '/<script(.*?)\/>/' );
		return preg_replace( $rules, array('', ''), $str );
	}
	
	//is validate telephone(固话)
	private static function isTelPhone ( &$str ) 
	{
		return (preg_match('/^[0-9]{4}\-[0-9]{7,8}$/', $str ) == 1);
	}
	
	/**
	 * Check the input data.
	 * 
	 *     For example,
	 *     check($_var, 
	 *           array(STRING, SLIMIT(6, 20), SANITIZE_TRIM|MAGIC_QUOTES|SANITIZE_HTML), 
	 *           $errno
	 *     )
	 *     //$_var == 'value' or $_var == 'Value':
	 *     check($_var, 
	 *           array(EQUAL, 'value'), 
	 *           $errno
	 *     )
	 *     /$_var in values 'value1,value2 ...':
	 *     check($_var, 
	 *           array(IN_VALUES, 'value1,value2 ...'), 
	 *           $errno
	 *     )
	 *     
	 * @param string   $val
	 * @param array    $model the type, length and sanitizing of the data
	 * @param string  $errno error number, invoke only when error occurs(return FALSE)
	 *                     0:for type error; 
	 *                     1:for length or size error; 
	 *                     
	 * @return string|FALSE
	 */
	private static function check(&$val, &$model, &$errno)
	{
	    //Step 1, check the data type
	    if ($val == NULL && ($model[0] & SNULL) != 0) return '';
	    
	    if (($model[0] & EQUAL)        != 0    && $val != $model[1]) {
	        return FALSE;
	    }
		if (($model[0] & EQUAL_LOWER)  != 0    && strtolower($val) != strtolower($model[1])) {
		    return FALSE;
		}
		if (($model[0] & IN_VALUES)    != 0    && strpos($model[1], ',')) {
			$vals = explode(',', $model[1]);
			if (! in_array($val, $vals)) return FALSE;
		}
		
		$errno = 0;
		
		if (($model[0] & STRING)      != 0 &&   self::isWhiteSpace($val)) return FALSE;
		if (($model[0] & NUMERIC)     != 0 && ! is_numeric($val))         return FALSE;
		if (($model[0] & LATIN)       != 0 && ! self::isLatin($val))      return FALSE;
		if (($model[0] & PHONE)       != 0 && ! self::isPhone($val))      return FALSE;
		if (($model[0] & EMAIL)       != 0 && ! self::isEmail($val))      return FALSE;
		if (($model[0] & QQ)          != 0 && ! self::isQq($val))         return FALSE;
		if (($model[0] & URL)         != 0 && ! self::isUrl($val))        return FALSE;
		if (($model[0] & DATE)        != 0 && ! self::isDate($val))       return FALSE;
		if (($model[0] & ZIP)         != 0 && ! self::isZip($val))        return FALSE;
		if (($model[0] & IDNUM)       != 0 && ! self::isIdnum($val))      return FALSE;
		if (($model[0] & TEL_PHONE)   != 0 && ! self::isTelPhone($val))   return FALSE;
	    
		$errno = 1;
		
	    //Step 2, check the data length
		if(is_array($model[1]) && !empty($model[1])){
		    /* if (!extension_loaded('mbstring')) {
		        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
		            dl('php_mbstring.dll');
		        } else {
		            dl('php_mbstring.so');
		        }
		    } 
		    $len = mb_strlen($val,'utf-8');
		    */
		    
		    switch ($model[1][0]) {
		        case 0 :  //data length limit
		            $len = strlen($val);
		            if ($len < $model[1][1])      return FALSE;
		            if (count($model[1]) == 3) {
		                if ($len > $model[1][2])  return FALSE;                   
		            }
		            break;
		        case 1 :  //data size limit
		            if ($val < $model[1][1])      return FALSE;
		            if (count($model[1]) == 3) {
		                if ($val > $model[1][2])  return FALSE;
		            }
		            break;
		    }
		}
		
	    //Step 3, sanitize the data
	    if ($model[2] == NULL) return $val;
	    if (($model[2] & SANITIZE_HTML)   != 0)    $val = htmlspecialchars($val);
	    if (($model[2] & SANITIZE_INT)    != 0)    $val = intval($val);
	    if (($model[2] & SANITIZE_TRIM)   != 0)    $val = trim($val);
	    if (($model[2] & SANITIZE_SCRIPT) != 0)    $val = self::sanitizeScript($val);
	    if (($model[2] & MAGIC_QUOTES)    != 0 && ! get_magic_quotes_gpc() ) $val = addslashes($val);
	    
		return $val;
	}
	
	/**
	 * @see self::check
	 * 
	 * @param string   $var
	 * @param array    $model
	 * @param integer  $errno
	 * 
	 * @return Ambigous <string, FALSE>
	 */
	public static function filterVar($var, $model, &$errno)
	{
	    return self::check($var, $model, $errno);
	}
	
	/**
	 * Load array from a specified variable according to the model. 
	 * 
	 * @param array $arr
	 * @param array $module
	 * @param array $errorn, for example: array('username',0) 
	 *                         0:for type error; 1:for length or size error;
	 * 
	 * @return boolean|array
	 */
	public static function filterArr( &$arr , $module , &$errorn = null ) {
		$_data    = array();
		$_errVal = 0;
		
		foreach( $module as $key => $value ) {
			$_ret = self::check($arr[$key], $value, $_errVal);
			
			if ($_ret === FALSE) {
			    if ($errorn !== null) $errorn = array($key, $_errVal);
			    return FALSE;
			}
			
			$_data[$key] = $_ret;
		}
		
		return $_data;
	}
}